<?php
/**************************
Socket机制封装的HTTP请求与接收
默认是GET的方式请求
lsj 2009-5-4
*************************/

class http
{
	protected $httpVersion = 1.1;		  #HTTP版本
	protected $requestMod = 'GET';		  #HTTP方式
	protected $requestPort = 80;		  #HTTP端口
	protected $timeOut = 20;			  #超时时间（秒）

	protected $requestHead = NULL;		  #头内容
	protected $requestHost = NULL;		  #请求时头内容中的host名
	protected $requestPath = NULL;		  #请求时头内容中的path名
	protected $requestURL = NULL;		  #请求时头内容中的URL地址

	protected $requestReferer = NULL;	  #请求时头内容中的REFERER
	protected $requestIP = NULL;		  #请求时头内容中的IP
	protected $requestCookie = NULL;	  #请求时头内容中的COOKIE
	protected $requestUserAgent = NULL;	  #请求时头内容中的客户信息

	private $responseResult = NULL;		  #返回的全部结果
	private $responseHead = NULL;		  #返回的头内容
	private $responseBody = NULL;		  #返回的正文内容

	private $hasHead = 0;				  #是否已经把头内容取走
	private $hasBody = 0;				  #是否已经把体头内容取走
	private $debug = 0;					  #DEBUG开关
	protected $errno = NULL;			  #错误编号
	protected $error = NULL;			  #错误信息
	private $fp = NULL;					  #SOCKET连接标识
	private $trackLine = 0;


	public function __construct($mod = 'GET')
	{
		$this->setHttp($mod);
	}

	/*
	#设置是否进入调试状态
	#$is: 1 on yes TRUE | 0 off no FALSE
	#返回: TRUE | FALSE
	*/
	public function isDebug($is = 1)
	{
		if($is === 1 || strtolower($is) === 'on' || strtolower($is) === 'yes' || $is === TRUE){
			$this->debug = 1;
		} else {
			$this->debug = 0;
		}
		return TRUE;
	}

	/*
	#设置HTTP协议、端口、方式
	#$mod: HTTP协议版本
	#$port: 请求端口
	#$time: 网络延迟时间
    #$version: HTTP协议版本
	#返回: TRUE | FALSE
	*/
	public function setHttp($mod = 'GET', $port = 80, $time = 20, $version = 1.1)
	{
		$mod = strtoupper($mod);
		$port = (int)$port;
		$time = (int)$time;
		$version = (float)$version;

		if(1.1 !== $version && 1.0 !== $version){
			$this->errno = 1;
			return FALSE;
		}

		if('GET' !== $mod && 'POST' !== $mod){
			$this->errno = 2;
			return FALSE;
		}

		if($port <= 0){
			$this->errno = 3;
			return FALSE;
		}

		$this->requestMod = $mod;
		$this->httpPort = $port;
		if($time > 0) $this->timeOut = $time;
		$this->httpVersion = $version;

		return TRUE;
	}

	/*
	#设置头信息
	#$referer: 来源页面地址
	#$ip: 客户端IP
	#$cookie: COOKIE信息
	#$userAgent: 客户端信息(浏览器与操作系统)
	#返回: TRUE | FALSE
	*/
	public function setHead($referer = '', $ip = '', $cookie = '', $userAgent = '')
	{
		if('' != $referer){
			if('http://' != strtolower(substr($referer, 0, 7))){
				$referer = 'http://'.$referer;
			}
			$this->requestReferer = $referer;
		}

		if('' != $ip){
			$this->requestIP = $ip;
		}

		if('' != $cookie){
			$this->requestCookie = $cookie;
		}

		if('' != $userAgent){
			$this->requestUserAgent = $userAgent;
		}

		return TRUE;
	}

	/*
	#发送SOCKET请求
	#$url:　请求的URL，可以有参数(如：?a=1&b=2&c=3)
	#$get:　GET参数，字符串或数组，会附在URL的后边
	#$post: POST参数，字符串或数组
	#$timeOut: SOCKET连接超时时间（秒）
	#返回： FALSE | TRUE
	*/
	public function request($url, $get = '', $post = '', $timeOut = 20)
	{
		if('' == $url){
			$this->errno = 4;
			return FALSE;
		} else {
			if('http://' != strtolower(substr($url, 0, 7))){
				$url = 'http://'.$url;
			}
		}

		if($timeOut > 0) $this->timeOut = $timeOut;

		$get = $this->toDataString($get);
		$post = $this->toDataString($post);

		if(!empty($get)){
			$url .= ((FALSE === strpos($url, '?')) ? '?' : '&') . $get;
		}
		$this->requestURL = trim($url);

		$target = parse_url($this->requestURL);

		$path = array_key_exists('path', $target) ? $target['path'] : '/';
		$path .= array_key_exists('query', $target) ? '?'.$target['query'] : '';
		$port = array_key_exists('port', $target) ? $target["port"] : $this->requestPort;

		#赋给属性，好让其它方法调用
		$this->requestHost = $host;
		$this->requestPath = $path;
		$this->requestPort = $port;

		#构造HTTP头内容
		$this->createHead($target['host'], $path, $post);

		#调试状态
		if($this->debug){
			echo "/****\r\nSend:\r\n" . $this->requestHead;
		}

		#SOCKET连接
		if(!$this->fp = @fsockopen($target['host'], $port, $socketErrno, $socketErrstr, $this->timeOut)){
			$this->errno = $socketErrno;
			$this->error = $socketErrstr;
			return FALSE;
		}

		#写入请求
		fwrite($this->fp, $this->requestHead);

		return TRUE;
	}

	/*
	#取回数据
	#$m: 返回结果的内容（head | body | status | all | line）　
	#返回： FALSE | STRING
	*/
	public function response($m = 'all')
	{
		if(!$this->fp){
			return FALSE;
		}

		$m = strtolower($m);
		$isChunk = 0; #是否要分段传输
		$chunk = NULL;#分段的数据长度
		$flag = $this->hasHead; #区分出头与正文的标识
        $isEncoding = 0;#是否取传输方式的标识

		if(1 === $this->hasHead && 0 === $isEncoding){
			$encoding = $this->getResponseHead('transfer-encoding');
			if($encoding && 'chunked' == $encoding){
				$isChunk = 1;
			}
            $isEncoding = 1;
		}

		if('status' === $m && NULL !== $this->responseHead){
			return $this->getResponseHead('status');
		}

		while(!feof($this->fp)){#按是否分段传输，控制取数据的方式
			if(1 === $isChunk){#分段传输的取法
                if(0 < $chunk){#已取到的分段长度
                    if('line' === $m){#只读一行
                      $result = fgets($this->fp, 4096);
                      $chunk -= strlen($result);
                      if(0 >= $chunk){
                            $chunk = hexdec(trim(fgets($this->fp, 4096)));
        				    $chunk = intval($chunk);
                      }
                    }else{
                        $result = fread($this->fp, $chunk);
        				fgets($this->fp, 4096);#读一个空行，将指针移到下一行，下一行即是长度值
        				$chunk = hexdec(trim(fgets($this->fp, 4096)));
        				$chunk = intval($chunk);
                    }
                } else {#分段传输时，首行肯定是长度值，走此条件，取出长度值
                    $result = fgets($this->fp, 4096);
                    if(eregi("^[a-f0-9]+$", trim($result))){
    					$chunk = hexdec(trim($result));
    					$chunk = intval($chunk);
    					if(0 < $chunk) continue;
    				}
                }
			} else {#非分段传输的取法
				$result = fgets($this->fp, 4096);
			}

			#取头内容
			if(0 === $flag){
				#只返回状态码
				if('status' === $m){
					$this->responseHead = $result;
					$this->responseResult = $result;
					return intval(str_replace(array(' ', 'http/'.$this->httpVersion), '', strtolower($result)));
				}

				#头结束
				if("\r\n" == $result || "\n" == $result){
					#调试状态
					if($this->debug){
						echo "\r\nReceive:\r\n" . $this->responseHead . "****/\r\n";
					}
					if ('head' === $m){
						$this->hasHead = 1;
						return $this->responseHead;
					} else {
					    $this->hasHead = 1;
						$flag = 1;
						continue;
					}
				}

				#头内容
				$this->responseHead .= $result;

				#判断是否分段传输模式
				if(false !== strpos(strtolower($result), 'transfer-encoding: chunked')){
					$isChunk = 1;
				}
			}

			#取正文内容
			if (1 === $flag){
				$this->responseBody .= $result;
			}

			#获取所有返回结果
			$this->responseResult .= $result;

			#返回当前行的结果
			if('line' === $m){
				return $result;
			}

			#结束读取
			if(0 === $chunk){
				$this->hasBody = 1;
				break;
			}
		}

		$this->hasBody = 1;
		if('body' === $m) return $this->responseBody;
        elseif('line' !== $m) return $this->responseResult;
	}

	/*
	#获取请求的头内容
	#key: 头内容中名称('' | 名称)
	#返回：STRING | NULL
	*/
	public function getRequestHead($key = '')
	{
		if('' !== $key && $this->requestHead){
			$key = strtolower($key);
			$header = str_replace("\r", '', $this->requestHead);
			$header = explode("\n", $header);
			if($header){
				$hd = array();
				foreach($header as $k => $val){
					$val = trim($val);
					if(empty($val)) continue;
					if(0 == $k){
						$hd['mode'] = trim(current(explode(' ', $val)));
						$hd[0] = $hd['mode'];
					}else{
						$d = explode(': ', $val);
						$d[0] = strtolower($d[0]);
						$hd[$d[0]] = $d[1];
						$hd[$k] = $hd[$d[0]];
					}
				}
				if(isset($hd[$key])) return $hd[$key];
			}
			return NULL;
		}
		return $this->requestHead;
	}

	/*
	#获取返回的头内容
	#key: 头内容中名称('' | 名称)
	#返回：STRING | NULL
	*/
	public function getResponseHead($key = '')
	{
		if(0 === $this->hasHead || !$this->responseHead){
			$this->responseHead = $this->response('head');
		}
		if('' !== $key && $this->responseHead){
			$key = strtolower($key);
			$header = str_replace("\r", '', $this->responseHead);
			$header = explode("\n", $header);
			if($header){
				$hd = array();
				foreach($header as $k => $val){
					$val = trim($val);
					if(empty($val)) continue;
					if(0 == $k){
						$hd['status'] = intval(str_replace(array(' ', 'http/'.$this->httpVersion), '', strtolower($val)));
						$hd[0] = $hd['status'];
					}else{
						$d = explode(': ', $val);
						$d[0] = strtolower($d[0]);
						if(isset($hd[$d[0]])){
                            if(is_array($hd[$d[0]])) $hd[$d[0]][] = trim($d[1]);
                            else{
                                $tmp = $hd[$d[0]];
                                $hd[$d[0]] = array();
                                $hd[$d[0]][] = $tmp;
                                $hd[$d[0]][] = trim($d[1]);
                            }
                        } 
                        else $hd[$d[0]] = trim($d[1]);
						$hd[$k] = $hd[$d[0]];
					}
				}
				if(isset($hd[$key])) return $hd[$key];
			}
			return NULL;
		}
		return $this->responseHead;
	}

	/*
	#获取返回的正文内容
	#$realpath：是否将内容中的相对路径给转换成绝对路径 （TRUE | FALSE）
	#返回：STRING | NULL
	*/
	public function getResponseBody($realpath = FALSE)
	{
		if(0 === $this->hasBody || !$this->responseBody){
			$this->responseBody = $this->response('body');
		}
		$html = $this->responseBody;
		if($realpath){
			$replace = array();
			$finder = array('src', 'href', 'action', 'url', 'background');
			//$html = str_replace(array("\r", "\n"), '', $html);
			foreach ($finder as $val){
				preg_match_all("|<.*".$val."\s*[=\(]\s*['\"]([^http:][^>]+)['\"]\s*|U", $html, $out);
				$replace = array_merge($replace, $out[1]);
			}
			$replace = array_unique($replace);
			if(count($replace) > 0){
				$tar = array();
				$target = parse_url($this->requestURL);
				$host = 'http://' . $target['host'];
				foreach ($replace as $vl){
					$vl = trim($vl);
					$vl = str_replace("\\", '/', $vl);
					if(false === strpos(strtolower(substr($vl, 0, 7)), 'http://')){
						$firster = substr($vl, 0, 1);
						if('/' == $firster){
							$tar[] = $host . $vl;
						}elseif('.' == $firster){
							$tar[] = $host . substr($vl, 1);
						}else{
							$tar[] = $host . '/' . $vl;
						}
					}
				}

				$html = str_replace($replace, $tar, $html);
			}
		}
		return $html;
	}

	/*
	#追踪返回结果
	#$m: 返回结果的内容（head | body | status | all | line）　
	#返回： FALSE | STRING
	*/
	public function trackResponse($m = 'all')
	{
		$status = '';
		$m = strtolower($m);
		$head = $this->getResponseHead();
		$status = $this->getResponseHead('status');
		$this->responseHead = $head;
		$this->hasHead = 1;

		if($status && (301 == $status || 302 == $status)){
			$next = $this->getResponseHead('location');
			if($next){
				$next = str_replace("\\", '/', trim($next));
				if(FALSE === strpos($next, '/')){
					$next = $this->requestPath . $next;
				}
				if(FALSE === strpos($next, $this->requestHost)){
					$next = $this->requestHost . $next;
				}
				$this->requestHead = '';
				$this->responseHead = '';
				$this->responseResult = NULL;
				$this->responseHead = NULL;
				$this->responseBody = NULL;
				$this->hasHead = 0;
				$this->close();
				$this->setHttp('GET', $this->requestPort, $this->timeOut, $this->httpVersion);
				$this->setHead($this->requestURL, $this->requestIP, $this->requestCookie, $this->requestUserAgent);
				$this->request($next);
				return $this->trackResponse($m);
			}
		}
		if('line' === $m){
			$hd = explode("\n", $head);
			if($this->trackLine <= count($hd)){
				return $hd[$this->trackLine++];
			}
		} elseif ('head' === $m){
			return $head;
		} elseif ('status' === $m){
			return $status;
		}

		return $this->response($m);
	}

	/*
	#生成http的请求头内容
	#$host: 请求的主机名
	#$path: 请求的主机地址
	#$postParams: 要POST传输的数据
	#返回: TRUE | FALSE | STRING
	*/
	public function createHead($host, $path, $postParams = '')
	{
		$this->requestHead = $this->requestMod.' '.$path.' HTTP/'.$this->httpVersion."\r\n";
		$this->requestHead .= 'Host: '.$host."\r\n";
        $this->requestHead .= "Accept: */*\r\n";
        $this->requestHead .= "Accept-Language: zh-cn,zh;q=0.5\r\n";
        $this->requestHead .= "Accept-Charset: GB2312,utf-8;q=0.7,*;q=0.7\r\n";
        
		if(NULL !== $this->requestReferer){
			$this->requestHead .= 'Referer: '.$this->requestReferer."\r\n";
		}

		if(NULL !== $this->requestUserAgent){
			$this->requestHead .= 'User-Agent: '.$this->requestUserAgent."\r\n";
		}

		if(NULL !== $this->requestIP){
			$this->requestHead .= "via: 1.1 JEJE1:80 (squid/2.5.STABLE4-NT-CVS)\r\n";
			$this->requestHead .= 'X-Forwarded-For: '.$this->requestIP."\r\n";
		}

		if(NULL !== $this->requestCookie){
			$this->requestHead .= 'Cookie: '.$this->requestCookie."\r\n";
		}

		if('POST' == $this->requestMod){
			$this->requestHead .= "Content-Type: application/x-www-form-urlencoded\r\n";
            $this->requestHead .= "Connection: close\r\n";
			$this->requestHead .= 'Content-Length: '.strlen($postParams)."\r\n\r\n";
			$this->requestHead .= $postParams."\r\n";
		}else{
			$this->requestHead .= "Connection: close\r\n\r\n";
		}

		#用来返回多个请求头内容
		$this->return_requestHead .= $this->requestHead;

		#返回当前当次请求头内容
		return $this->requestHead;
	}

	/*
	#把数组转成POST或GET形式的字符串
	#返回：STRING
	*/
	public function toDataString($arr)
	{
		$str = '';

		if(is_string($arr)){
			return trim($arr, '&');
		} elseif (is_array($arr)){
			foreach ($arr as $key => $val) {
				if($str == ''){
					$str .= $key . '=' . rawurlencode($val);
				} else {
					$str .= '&'.$key . '=' . rawurlencode($val);
				}
			}
		}

		return $str;
	}

	/*
	#关闭SOCKET连接
	#返回：TRUE | FALSE
	*/
	public function close()
	{
		if(NULL !== $this->fp && is_resource($this->fp)){
			$ok = fclose($this->fp);
			$this->fp = NULL;
			return $ok;
		}
		return TRUE;
	}

	/*
	#获取出错信息
	*/
	public function getError($errno = NULL)
	{
		$error = array(
			1 => 'Http version is not correct',
			2 => 'Way is not correct',
			3 => 'Port is not correct',
			4 => 'Url can\'t for empty',
		);

		if(NULL !== $this->errno){
			if($this->error !== NULL){
				return $this->error;
			}
			return $error[$this->errno];
		}
		if(NULL !== $this->errno){
			if($this->error !== NULL){
				return $this->error;
			}
			return $error[$errno];
		}

		return NULL;
	}

	/*
	#析构函数
	#关闭连接
	*/
	public function __destruct()
	{
		$this->close();
	}


}







?>
